﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MashGen.Engine.Expressions
{
    public sealed class Normalizer : IExpressionVisitor<AbstractExpression, object>
    {
        static Normalizer() { }

        private static Normalizer instance = new Normalizer();

        private Normalizer() { }

        public static AbstractExpression Normalize(AbstractExpression expression)
        {
            return expression.Vist(instance);
        }

        public static List<AbstractExpression> NormalizeSummands(IList<AbstractExpression> summands)
        {
            var normalizedSummands = new List<AbstractExpression>();
            foreach (var summand in summands)
            {
                normalizedSummands.Add(Normalize(summand));
            }
            instance.NormalizeSubElements<Sum>(normalizedSummands, s => s.NormalizedSummands);
            return normalizedSummands;
        }

        public static List<AbstractExpression> NormalizeFactors(List<AbstractExpression> factors)
        {
            var normalizedFactors = new List<AbstractExpression>();
            foreach (var factor in factors)
            {
                normalizedFactors.Add(Normalize(factor));
            }
            instance.NormalizeSubElements<Product>(factors, p => p.NormalizedFactors);
            return normalizedFactors;
        }


        AbstractExpression IExpressionVisitor<AbstractExpression, object>.VisitNumber(Number number, object parameter)
        {
            return number;
        }

        AbstractExpression IExpressionVisitor<AbstractExpression, object>.VisitVariable(Variable variable, object parameter)
        {
            return variable;
        }

        AbstractExpression IExpressionVisitor<AbstractExpression, object>.VisitNegative(Negative negative, object parameter)
        {
            var expression = negative.Expression.Vist(this);
            return new Negative(expression);
        }

        AbstractExpression IExpressionVisitor<AbstractExpression, object>.VisitSum(Sum sum, object parameter)
        {
            var summands = new List<AbstractExpression>();
            foreach (var summand in sum.Summands)
            {
                summands.Add(summand.Vist(this));
            }
            NormalizeSubElements<Sum>(summands, s => s.Summands);
            return new Sum(summands);
        }

        AbstractExpression IExpressionVisitor<AbstractExpression, object>.VisitProduct(Product product, object parameter)
        {
            var factors = new List<AbstractExpression>();
            foreach (var factor in product.Factors)
            {
                factors.Add(factor.Vist(this));
            }
            NormalizeSubElements<Product>(factors, p => p.Factors);
            return new Product(factors);
        }

        AbstractExpression IExpressionVisitor<AbstractExpression, object>.VisitQuotient(Quotient quotient, object parameter)
        {
            var numerator = quotient.Numerator.Vist(this);
            var denominator = quotient.Denominator.Vist(this);
            return new Quotient(numerator, denominator);
        }

        AbstractExpression IExpressionVisitor<AbstractExpression, object>.VistPower(Power power, object parameter)
        {
            var radix = power.Radix.Vist(this);
            var exponent = power.Exponent.Vist(this);
            return new Power(radix, exponent);
        }
        
        private void NormalizeSubElements<E>(List<AbstractExpression> elements, 
            Func<E, IList<AbstractExpression>> subElementAccessor)
            where E : AbstractExpression
        {
            for (int index = 0; index < elements.Count; index++)
            {
                var element = elements[index];
                var subElementContainer = element as E;
                if (subElementContainer != null)
                {
                    elements.RemoveAt(index);
                    foreach (var subElement in subElementAccessor(subElementContainer))
                    {
                        elements.Insert(index++, subElement);
                    }
                }
            }
            elements.Sort();
        }
    }
}
